#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <time.h>
#include <ncurses.h>
#include <pthread.h>
#include "client.h"
#include "../clsv/error.h"
#include "../superADT/transport/transADT.h"
#include "../superADT/sessADT.h"
#include "cl.h"
#include "../clsv/clsv.h"


static void siginthandler(int sig);
static void siginthandler2(int sig);

connInfoT connInfo= {'\0', '\0'};
char nickname[MAX_NICK]= "anonymous";
int _sess = -1, _intr = 0, _threadid = -1;

pthread_mutex_t mutexmsg;
messT mess;
TcliData cliData;
TcliData cliData2;
pthread_t _thread;

WINDOW * create_newwin(int height, int width, int starty, int startx);

int
main(int argc, char * argv[])
{
	int end= 0, ans;
	char * inpbuf;

	ans = getConf(argc, argv, &cliData);
	if( ans != ERROR_OK)
		return ans;

	if((inpbuf= malloc(MAXLINE+1)) == NULL)
	{
		fprintf(stderr, "Memory allocation error\n");
		return 1;
	}

	system("clear");
	printf("Welcome!\n\n");

	signal(SIGINT, siginthandler);

	initscr();
	cbreak();

	keypad(stdscr, TRUE);		/* Enable alternative keys F1 	*/
	noecho();

	idlok(stdscr, TRUE);
	scrollok(stdscr, TRUE);

	wresize(stdscr, LINES - 1, COLS);
	refresh();

	while (!end && input(PROMPT, inpbuf))
	{
		end= process_line(inpbuf);

		if( end && closeSess(cliData.sess) != ERROR_OK )
		{
			printw("Error closing session.\n");
			refresh();
		}
	}
	printw("Thanks for using the chat!\n\n");
	refresh();
	endwin();
	cliData.sess= NULL;
	free(inpbuf);

	return 1;
}


void *
getMessages(void * parameters)
{
	CLSV_BUFFER buff;
	_threadid = pthread_self();
	forever
	{
		getMsg(cliData.sess, &buff, sizeof(CLSV_BUFFER), 0);
		pthread_mutex_lock(&mutexmsg);
		if(mess.nomessage)
		{
			strcpy(mess.message, buff.data.svcl_chat.msg);
			strcpy(mess.nick, buff.data.svcl_chat.nick);
			mess.nomessage = 0;
			//printf("<%s> %s\n", mess.nick, mess.message)
		}

		pthread_mutex_unlock(&mutexmsg);
	}

}

/*----------------------------------------------------------------------------*/

int
getConf(int argc, char ** argv, TcliData *cliData)
{
	static Tfifo fifo;
	static Ttcp tcp;
	static Tshm shm;
	static int key;
	int index = 1;

	if( argc != 2 )
	{
		printf("Invalid parameters. Usage: %s "
				"client[Msg | Tcp | Shm | Fifo]\n", argv[0]);
		return ERROR_PARAMS;
	}

	if( strstr(argv[index], USE_FIFO_STR) != NULL )
	{
		cliData->ipc = SESS_FIFO;
		fifo.write = KEY_FIFO_SND;
		fifo.read = KEY_FIFO_RCV;
		cliData->dest = &fifo;
	}
	else if( strstr(argv[index], USE_TCP_STR) != NULL )
	{
		cliData->ipc = SESS_TCP;
		tcp.host = TCP_HOST;
		tcp.port = TCP_PORT;
		cliData->dest = &tcp;
	}
	else if( strstr(argv[index], USE_SHM_STR) != NULL )
	{
		cliData->ipc = SESS_SHM;
		shm.keySnd = KEY_SHM_SND;
		shm.keyRcv = KEY_SHM_RCV;
		cliData->dest = &shm;
	}
	else if( strstr(argv[index], USE_MSG_STR) != NULL )
	{
		cliData->ipc = SESS_MSG;
		key = KEY_MSG;
		cliData->dest = &key;
	}
	else
	{
		printf("Invalid parameters. Usage: %s "
				"client[Msg | Tcp | Shm | Fifo]\n", argv[0]);
		return ERROR_PARAMS;
	}
	
	return ERROR_OK;
}

void
exitCli(int s)
{
	closeSess(cliData.sess);
	cliData.sess= NULL;
	exit(ERROR_UNEX);
}

/*----------------------------------------------------------------------------*/


int
startConn()
{
	
	if(_sess < 0)
	{
		if(initSessClient(cliData.ipc, cliData.dest, &(cliData.sess))
									!= ERROR_OK )
		{
			printw("Error initiating session.\n");refresh();
			return -1;
		}
	}
	else
	{
		memcpy(&cliData2, &cliData, sizeof(TcliData));
		if(initSessClient(cliData2.ipc, cliData2.dest, &(cliData2.sess))
									!= ERROR_OK )
		{
			printw("Error initiating session.\n");refresh();
			return -1;
		}
	}

	return 1;
}


int
listSessions(void)
{
	CLSV_BUFFER buf;
	TcliData * pclidata;
	buf.opcode = LISTS;
	//buf.data.clsv_lists.upid = getpid();
	
 	startConn();
	if(_sess < 0)
		pclidata = &cliData;
	else
		pclidata = &cliData2;
		
	sendMsg(pclidata->sess, &buf, sizeof(CLSV_BUFFER));
	sleep(1);
	getAns(pclidata->sess, &buf, sizeof(CLSV_BUFFER), 0);
	while(_sess > 0 && buf.opcode == CHATS)
	{
		printw("<%s> %s\n", buf.data.svcl_chat.nick, buf.data.svcl_chat.msg);
		getAns(cliData.sess, &buf, sizeof(CLSV_BUFFER), 0);
	}

	if(buf.data.svcl_lists.status == OK_)
	{
		printw("List of session numbers:\n%s\n", buf.data.svcl_lists.list);
		refresh();
	}
	else
		myError(buf.data.svcl_lists.status);
	if(_sess > 0)
	{
		if( cliData.ipc == SESS_TCP )
			closeSess(pclidata->sess);
		pclidata->sess = NULL;
	}
	
	return 1;
}

int
listUsers(char * session)
{
	CLSV_BUFFER buf;
	int room;
	TcliData * pclidata;

	room = atoi(session);

	if(room < 1 || room > 90)
	{
		printw("Error: The number of session is limited between 1 and "
						"90.\n");
		refresh();
		return -1;
	}

	buf.opcode = LISTU;
	buf.data.clsv_listu.session = room;
	//buf.data.clsv_listu.upid = -1;

	startConn();
	if(_sess < 0)
		pclidata = &cliData;
	else
		pclidata = &cliData2;
	sendMsg(pclidata->sess, &buf, sizeof(CLSV_BUFFER));
	sleep(1);	
	getAns(pclidata->sess, &buf, sizeof(CLSV_BUFFER), 0);
	while(_sess > 0 && buf.opcode == CHATS)
	{
		printw("<%s> %s\n", buf.data.svcl_chat.nick, buf.data.svcl_chat.msg);
		getAns(cliData.sess, &buf, sizeof(CLSV_BUFFER), 0);
	}

	if(buf.data.svcl_listu.status == OK_)
	{
		printw("List of users for the session %s:\n%s\n", session,
													buf.data.svcl_listu.list);
		refresh();
	}
	else
		myError(buf.data.svcl_listu.status);
	
	if(_sess > 0)
	{
		if( cliData.ipc == SESS_TCP )
			closeSess(pclidata->sess);
		pclidata->sess = NULL;
	}
	
	return 1;
}

void
close_session()
{
	CLSV_BUFFER buf;

	if(_sess > 0)
	{
		buf.opcode = DISCONNECT;
		buf.data.clsv_disc.session = _sess;
		strcpy(buf.data.clsv_disc.nick, nickname);
		startConn();

		sendMsg(cliData.sess, &buf, sizeof(CLSV_BUFFER));

		//cerrar la conexion con el servidor
		if( _sess > 0 && closeSess(cliData.sess) != ERROR_OK )
		{
			printw("Error closing session.\n");
			refresh();
		}
		if( _sess > 0 )
		{
			closeSess(cliData2.sess);
			cliData2.sess = NULL;
			cliData.sess= NULL;
		}
		_sess = -1;
	}
}

int
enter(char * session)
{
	CLSV_BUFFER buf;
	int aux, end=0;
	char inpbuf[MAXLINE+1];
	int auxsess;

	if(_sess > 0)
	{
		printw("You are already logged in to a session. Type %s to "
		"quit the session first.\n", STRQUIT);
		refresh();
		return 1;
	}

	auxsess = atoi(session);

	if(auxsess < 1 || auxsess > 90)
	{
		printw("Error: The number of session is limited between 1 and "
						"90.\n");
		refresh();
		_sess = -1;
		return -1;
	}

	buf.opcode = CONN;
	buf.data.clsv_conn.session = auxsess;
	//buf.data.clsv_conn.upid = getpid();
	strcpy(buf.data.clsv_conn.nick, nickname);

	startConn();
	sendMsg(cliData.sess, &buf, sizeof(CLSV_BUFFER));
	sleep(1);	
	getAns(cliData.sess, &buf, sizeof(CLSV_BUFFER), 0);
	mess.nomessage = 1;
	_sess = auxsess;
	
	if(buf.data.svcl_conn.status == OK_)
	{	
		while(!end && doChat(&_sess) > 0)
		{
			if(_intr)
			{
 				if(input(PROMPT, inpbuf))
 				{
 					end = process_line(inpbuf);
 					_intr = 0;
 				}
			}
		}
	}
	else
	{
		close_session();
		myError(buf.data.svcl_conn.status);
	}
	return 1;
}



int
setNick(char * nick)
{
		
	if(_sess > 0)
	{
		printw("Error: can't change nick while logged in to a session.\n");
		refresh();
	}
	else		
	{
		if( strlen(nick) > MAX_NICK )
		{
			printw("The nickname is too long. Please enter another one with less "
					"than %d characters.\n", MAX_NICK);
			refresh();
		}
		else
			strcpy(nickname, nick);
	}

	return 1;
}

int
sendMessage(char *line)
{
	CLSV_BUFFER buf;

	buf.opcode = CHATR;
	strcpy(buf.data.clsv_chat.msg, line);
	strcpy(buf.data.clsv_chat.nick, nickname);
	//buf.data.clsv_chat.upid = -1;

	sendMsg(cliData.sess, &buf, sizeof(CLSV_BUFFER));

	return 1;
}


void
destroy_win(WINDOW *local_win)
{
	wborder(local_win, ' ', ' ', ' ',' ',' ',' ',' ',' ');

	wrefresh(local_win);
	delwin(local_win);
}

int
doChat(int * session)
{
	WINDOW * win;
	int ch, i = 0, x = 0, y = 0;
	char line[80];

	signal(SIGINT, siginthandler2);

	win =  create_newwin(1, 83, LINES - 1, 0);
	keypad(win, TRUE);
	scrollok(win, FALSE);
	nodelay(win, TRUE);

	pthread_mutex_init(&mutexmsg, NULL);
	if(pthread_create(&_thread, NULL, getMessages, NULL) != 0)
		fatal("Error: can't create new thread");

	while(!_intr)
	{
		ch = wgetch(win);
		switch(ch)
		{
			case '\n':  line[i] = '\0';
						//addstr(line);
						//addch('\n');
						sendMessage(line);
						werase(win);
						wrefresh(win);
						refresh();
						i = 0;
						break;

			case ERR: 	pthread_mutex_lock(&mutexmsg);
						if(!mess.nomessage)
						{
							printw("<%s> %s\n", mess.nick, mess.message);
							refresh();
							mess.nomessage = 1;
						}
					  	pthread_mutex_unlock(&mutexmsg);
						break;

			case  KEY_BACKSPACE:
						if(i > 0)
						{
							line[--i] = '\0';
							getyx(win, y, x);
							--x;
							wmove(win, y, x);
							wdelch(win);
							wrefresh(win);
						}
						break;

			default:
						if(i < 79)
						{
							line[i++] = ch;
							waddch(win, ch);
							wrefresh(win);
						}
		}
	}
	destroy_win(win);
	signal(SIGINT, siginthandler);
	pthread_mutex_destroy(&mutexmsg);
	pthread_kill(_thread, SIGINT);
	return 1;
}



WINDOW *
create_newwin(int height, int width, int starty, int startx)
{
	WINDOW *local_win;

	local_win = newwin(height, width, starty, startx);
	//box(local_win, 0 , 0);
	wrefresh(local_win);		/* Show the box*/

	return local_win;
}

/* Extra functions */
static void
siginthandler(int sig)
{
	if( _threadid != -1)
	{
		_threadid = -1;
		pthread_exit(NULL);
	}
	else
	{
		_intr = 1;
		printw("Thanks for using the chat!\n\n");
		refresh();
		endwin();
		exit(1);
	}
}

static void
siginthandler2(int sig)
{
	_intr = 1;
}
